Decorators are a way to do aspect oriented programming in Python. Decorators were added in Python 2.4 and many libraries make extensive use of them.
In the simplest form
@dec
def foo():
pass
is equivalent to
def foo():
pass
foo = dec(foo)
For the above you can see that a decorator is a function that takes a function as an argument and returns a function.
Since their introduction in Python 2.4, decorators are everywhere. Including some in the standard library: property, staticmethod and others. Many libraries also use decorators: Celery @task
, Flask @app.route
and many more.
Decorators can be nested
@logged
@metrics.ncalls
@retry(times=3)
def whos_on_first():
return 'who'
There are also class decorators (added in Python 2.6). You will probably won't need them (unless you wrote metaclasses before).
@plugin
class FunnyPlugin(object):
pass
In [2]:
from functools import wraps
def greeter(fn):
@wraps(fn)
def wrapper(*args, **kw):
print('Hello {}'.format(fn.__name__))
try:
return fn(*args, **kw)
finally:
print('Bye {}'.format(fn.__name__))
return wrapper
In [3]:
@greeter
def add(x, y):
'''Adds x to y'''
return x + y
print(add(1, 2))
In [18]:
from time import time
def timed(fn):
@wraps(fn)
def wrapper(*args, **kw):
start = time()
try:
return fn(*args, **kw)
finally:
duration = time() - start
print('{} took ({:.2f}sec)'.format(fn.__name__, duration))
return wrapper
In [19]:
from time import sleep
@timed
def mul(x, y):
'''Multiply x with y'''
sleep(0.2)
return x * y
mul(8, 4)
# return 32 and print timing info
Out[19]:
In [8]:
from ctx import timed_block
def timed(fn):
@wraps(fn)
def wrapper(*args, **kw):
with timed_block(fn.__name__):
return fn(*args, **kw)
return wrapper
Write a decorator that caches the results of the invoked function. Computing every value only once. (See lru_cache in Python > 3.2).
In [1]:
def cached(fn):
cache = {} # In scope for wrapper
def wrapper(*args, **kw):
if args not in cache: # Ignore **kw for simplicity
cache[args] = fn(*args, **kw)
return cache[args]
return wrapper
In [2]:
@cached
def fib(n):
'''Return the n'th fibonacci number'''
print('fib({})'.format(n))
if n < 2:
return 1
return fib(n-1) + fib(n-2)
fib(5)
Out[2]:
In [27]:
def mulby(n):
def wrapper(fn):
@wraps(fn)
def wrapped(*args, **kw):
return n * fn(*args, **kw)
return wrapped
return wrapper
In [28]:
@mulby(7)
def inc(x):
'''Add 1 to x'''
return x + 1
inc(2) # 21
Out[28]:
property makes a function looks like an ordinary attribute. It let's you change your mind and have more controlled access to attribute (say locking) without any change to the client.
In [4]:
class Parrot(object):
def __init__(self, voltage):
self._voltage = voltage
@property
def voltage(self):
return self._voltage
p = Parrot(10)
print(p.voltage) # Note: Not a function call
In [ ]: